/*
 * Copyright (C) 2011 : Gilles Coomans 
 * 
 * This file is part of QuintEssence.
 * QuintEssence is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * QuintEssence is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with QuintEssence.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.exprom.qe.fields;
//import net.exprom.qe.IQELayerable;
//import net.exprom.qe.IQELayerable;
import net.exprom.qe.QEStack;
import net.exprom.qe.QETools;

/**
 * ABSTRACT CLASS
 * 
 * 	- fixAfterValuation:Bool = true	// que lors des applications : ce champ participe à l'application. Si false : la valeur de ce champ reste propre à chaque instance de champ ==> Sauf la première fois : simili inférence de type
	- clonable:Bool = true,		// la valeur peut être clonée, et ne participe pas au JSONObject final
	- finalRequired:Bool = false, 	// le champ doit AU FINAL être défini (donc dans le Model lors de la validation finale). Dans les layers : n'est pas obligatoirement présent. 
	- layerRequired:Bool = false, 	// le champ doit être présent dans toutes layers.
	- evaluable:Bool = false		// peut etre vu comme un type : indique que la valeur peut etre une fonction ou un LateCall : donc quelque chose d'évaluable dynamiquement au run time ==> MACROS
	- type:Dynamic = null;		// sert au typage proprement dit : un typage par expression régulière (ou par reférence vers EReg prédéfinie), par fonction(fn(val):Bool), ou si on donne un immédiat : c'est l'équivalent au "final"... 
	- removable:true // peut etre retiré du MetaModel 
	- customCall:{
		applyLayerField(),
		applyModelField(),
		testType(),
		cloneField(),
		evaluate()
	}
 * 
 * @author Gilles Coomans
 */
enum QEFieldType 
{
	QEFT_LEAF; 
	QEFT_METAS_ONLY;
	QEFT_NODE;
	QEFT_NODE_LIST;
}

enum QEFieldVisibility
{
	QEFV_PUBLIC;
	QEFV_METAS_ONLY;
	QEFV_HIDDEN;
}

class QEField 
{
	public var evaluated:Bool;
	public var parent:QEStack;
	public var rootContext:QEStack;
	public var namespace:String;
	public var path:String;
	public var name:String;
	
	public var fieldType:QEFieldType;
	private var metasStack:QEStack;
	public var parameters:Dynamic;
	public var setted:Bool;
	
	//___________________________________________ FIELD'S PARAMS
	//public var fixAfterValuation:Bool;
	//public var clonable:Bool;
	//public var removable:Bool;
	//public var finalRequired:Bool;
	//public var layerRequired:Bool;
	//public var customCall:Dynamic;
	//public var visibility:QEFieldVisibility;
	//public var finaliseMetas:Bool;
	
	

	public function new(name:String, parent:QEStack, params:Dynamic = null) 
	{
		this.parent = parent;
		this.name = name;

		//__________________________________________
		createParamStruct();
		if (params != null)
			applyParameters(params);
	}
	
	private function createParamStruct():Dynamic
	{
		fieldType = QEFT_METAS_ONLY;
		evaluated = false;
		parameters = {
			visibility:QEFV_PUBLIC, 
			fixAfterValuation:true, 
			clonable:true, 
			removable:true,
			finalRequired:false,
			layerRequired:false, 
			customCall: { },
			finaliseMetas:false
		};
		return parameters;
	}
	
	public function applyParameters(params:Dynamic, up:Bool = true, commonOnly:Bool = false):Void
	{
		if (up)
		{
			if (Reflect.hasField(params, "fixAfterValuation")) 
				parameters.fixAfterValuation  = params.fixAfterValuation;
			if (Reflect.hasField(params, "clonable")) 
				parameters.clonable  = params.clonable;
			if (Reflect.hasField(params, "removable")) 
				parameters.removable  = params.removable;
			if (Reflect.hasField(params, "finalRequired")) 
				parameters.finalRequired  = params.finalRequired;
			if (Reflect.hasField(params, "layerRequired")) 
				parameters.layerRequired  = params.layerRequired;
			if (Reflect.hasField(params, "finaliseMetas")) 
				parameters.finaliseMetas  = params.finaliseMetas;
			if (Reflect.hasField(params, "customCall")) 
				QETools.mergeObject(parameters.customCall, params.customCall, up);
		}
		if (!commonOnly && Reflect.hasField(params, "metasModel")) 
		{
			if (Std.is(params.metasModel, Array))
			{
				var arr:Array<Dynamic> = params.metasModel;
				if (!up)
					arr.reverse();
				for(mm in arr)
					applyMetasModel(mm, up, commonOnly);
			}
			else
				applyMetasModel(params.metasModel, up, commonOnly);
		}
	}
	
	public function setParent(parent:QEStack):Void
	{
		this.parent = parent;
		if (hasSettedMetas())
			metasStack.setParent(parent);
	}
		
	public function setNamespace(namespace:String, name:String = null):Void
	{
		if(name != null)
			this.name = name;
		this.namespace = namespace;
		path = namespace + "." + this.name;
		if (metasStack != null)
			metasStack.setNamespace(path);
	}
	
	public function applyLayer(layer:Dynamic, up:Bool = true, commonOnly:Bool = false):Void
	{
		#if QEC_DEBUG message("applyLayer()"); #end
	}

	inline public function applyMetasModel(model:QEStack, up:Bool = true, commonOnly:Bool = false):Void
	{ 
		#if QEC_DEBUG message("", "applyMetasModel()", model); #end
		getMetasStack().applyStack(model, up, commonOnly);
	}
	
	public function applyMetasLayer(layer:Dynamic, up:Bool = true, commonOnly:Bool = false):Void
	{
		#if QEC_DEBUG message("applyMetasModel()"); #end
		getMetasStack().applyLayer(layer, up, commonOnly);
	}
	
	inline public function getMetasStack():QEStack
	{
		if (metasStack == null)
		{
			metasStack = new QEStack(path, "metasStack");
			metasStack.IS_METAS = true;
			metasStack.setParent( parent );
			metasStack.rootContext = rootContext;
		}
		return metasStack;
	}
	
	inline public function hasSettedMetas():Bool
	{
		return (metasStack != null);
	}
	
	//_______________________________________________________________ VALIDATION

	public function validate():Bool
	{
		var ok = true;
		if (hasSettedMetas())
			ok = metasStack.validate();
		return ok;
	}
	
	public function clone(cloned:Dynamic = null):Dynamic
	{
		#if QEC_DEBUG message("clone()"); #end
		if(cloned == null)
			cloned = new QEField(this.name, null);
		//cloned.parameters = QETools.cloneStruct(parameters);	
		cloned.fieldType = this.fieldType;
		cloned.compilField(this);
		return cloned;
	}
	
	public function compilField(field:QEField, up:Bool = true, commonOnly:Bool = false):Void
	{
		#if QEC_DEBUG message("compilField() : " + field.name); #end
		if (field.fieldType != fieldType)
			throw "Error : could not merge fields with differents types when applying : " + field.path + " on " + path;
		setted = field.setted;
		if (field.hasSettedMetas())
		{
			//trace("while compiling field : " + field.path + " - on " + path + " - has setted metas so applying");
			getMetasStack().applyStack(field.getMetasStack(), up, commonOnly);
		}
		applyParameters(field.parameters, up, commonOnly);
	}
	
	public function compil():Void
	{
		#if QEC_DEBUG message("compil()"); #end
		if (hasSettedMetas())
			getMetasStack().compil();
	}
	
	public function resetLeafs():Void
	{
		setted = false;
	}
	
	inline public function resetMetasLeafs():Void
	{
		if (hasSettedMetas())
			metasStack.resetLeafs();
	}
	
	public function toXML(query:Dynamic):Xml
	{
	//	var xml:Xml = new Xml();
	//	xml.addChild(
		return null;
	}
	
	public function toXMLString():String
	{
		return "";
	}
	
	public function finalStruct(includeMetas:Bool = false, fixEvaluation:Bool = false, validateLeafs:Bool = false, includeDefaults:Bool = false):Dynamic
	{
		#if QEC_DEBUG message("finalStruct()"); #end
		var o = { _METAS: { }};
		Reflect.setField(o, name, null);
		Reflect.setField(o._METAS, name, null);
		if (includeMetas && hasSettedMetas())
		{
			getMetasStack().compil();
			var finalMetas = getMetasStack().finalStruct( includeMetas, fixEvaluation, validateLeafs, includeDefaults);
			Reflect.setField(o._METAS, name, finalMetas);
		}
		return o;
	}
	
	private function message(msg:String, app:String = null, ref:Dynamic = null)
	{
		var r = "QEField : " + path + " : "+fieldType + " : ";
		if (app != null)
			r += app;
		if (ref != null)
		{
			if (Std.is(ref, String))
				r += " : " + ref;
			else
				r += " : " + ref.path;
		}
		r += " : " +msg;
		trace(r);
	}
	
	
	public function fixEvaluables():Void
	{
		if (evaluated)
			return;
		if (hasSettedMetas())
			getMetasStack().fixEvaluables();
		evaluated = true;
	}
}